<?php

declare(strict_types=1);

namespace app\libs\token\driver;

use app\libs\token\Token;
use think\facade\Db;

/**
 * Token的MySql实现
 * 
 * 采用一个key对应一个token的模型，即模型表上有一个token字段，每条数据可以有一个token
 * 
 * 例子:
 * 
 $tokenApp = User::getTokenApp();

 #### 单点登录（创建token）
 $user = User::find(1);
 $tokenApp->create($user['id']);

 #### 获取某个用户已经存在的token
 $user_id = 1;
 $token = $tokenApp->token($user_id);

 #### 日常鉴权（token换取用户信息）
 $token = 'xxx';

 $user = $tokenApp->getData($token); // null 或 user模型

 if($user) $user->save(xxx);

 #### 更新token中的数据
 $user = User::find(1);

 $tokenApp->saveData($user['id'], $user);
 * 
 */
class MysqlToken extends Token
{

	public function __construct($config = [])
	{
		parent::__construct(array_merge([
			/** 表名 */
			'table' => '',
			/** token字段名 */
			'token_field' => 'token',
			/** 过期时间字段名，设为datetime类型，允许null */
			'expire_field' => 'token_expire_time',
		], $config));
	}

	protected function getPk()
	{
		return Db::table($this->config['table'])->getPk();
	}

	protected function queryToken(string $token)
	{
		return Db::table($this->config['table'])
			->where($this->config['token_field'], $token)
			->where(function ($query) {
				$query->whereNull($this->config['expire_field'])->whereOr($this->config['expire_field'], '>=', date('Y-m-d H:i:s'));
			});
	}

	/**
	 * 通过token直接获取该条数据
	 * @param string $token
	 * @return array 注意返回的是Db的查询结果，而不是模型，如需转成模型可以new一个把数据放进去，或者根据pk重新查询
	 */
	public function getData(string $token, $fields = null)
	{
		return $this->queryToken($token)->when(!is_null($fields), fn ($q) => $q->fields($fields))->find();
	}

	/**
	 * ---- 以下实现Token接口 ----
	 */
	public function get(string $token)
	{
		if (!$token) return null;
		return $this->queryToken($token)->value($this->getPk());
	}

	public function token($key, $all = false)
	{
		return Db::table($this->config['table'])->find($key)[$this->config['token_field']] ?? null;
	}

	public function create($key, int $expireTime = null, string $token = null)
	{
		$pk = $this->getPk();
		$data = Db::table($this->config['table'])->field([$pk])->findOrFail($key);

		if (is_null($token)) $token = $this->generateToken();
		if (is_null($expireTime)) $expireTime = $this->config['expire_time'];

		Db::table($this->config['table'])->where($pk, $data[$pk])->update([
			$this->config['token_field'] => $token,
			$this->config['expire_field'] => is_null($expireTime) ? null : date('Y-m-d H:i:s', time() + $expireTime),
		]);

		return $token;
	}

	public function has(string $token)
	{
		if (!$token) return null;
		return !!$this->queryToken($token)->value($this->getPk());
	}

	public function delete(string $token)
	{
		return !!$this->queryToken($token)->update([
			$this->config['token_field'] => '',
			$this->config['expire_field'] => null,
		]);
	}

	public function clear($key = null)
	{
		$pk = $this->getPk();
		return Db::table($this->config['table'])->when(
			!is_null($key),
			fn ($q) => $q->where($pk, $key),
			fn ($q) => $q->whereNotNull($pk)
		)->update([
			$this->config['token_field'] => '',
			$this->config['expire_field'] => null,
		]);
	}

	public function ttl(string $token, $expireTime = null)
	{
		$search = $this->queryToken($token)->find();

		// 获取过期时间
		if (is_null($expireTime)) {
			if (!$search) return false;
			// 返回和当前的秒数差
			return strtotime($search[$this->config['expire_field']]) - time();
		}

		// 设置过期时间
		if (!$search) return false;
		return $this->queryToken($token)->update([
			$this->config['expire_field'] => date('Y-m-d H:i:s', time() + $expireTime),
		]);
	}
}
